package com.wsc.frontier;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetSocketAddress;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.nio.DefaultClientIOEventDispatch;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.nio.NHttpConnection;
import org.apache.http.nio.protocol.BufferingHttpClientHandler;
import org.apache.http.nio.protocol.EventListener;
import org.apache.http.nio.protocol.HttpRequestExecutionHandler;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.SessionRequest;
import org.apache.http.nio.reactor.SessionRequestCallback;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.log4j.Logger;

public class FrontierClient {
	private String resonpharse,protocolversion;
	int statuscode;
//	Logger
	private static Logger log=Logger.getLogger(FrontierClient.class.getName());
	
	
	    public void setresponsedetails(String rpharse,String rversion,int rcode){
	        resonpharse = rpharse;
	        protocolversion = rversion;
	        statuscode = rcode;
	    }
	    
	        
	    public void sendRequest(String hostname,int port,final String requesttype) throws Exception {
	        //creating a logfilename to record this class actions
	        Date dt = new Date();
	        DateFormat df = new SimpleDateFormat("yyyy-mm-dd");
	        String ControllerClientlogfile = "ControllerClient"+df.format(dt);
	        log.info("Sending Request details\n");
	        log.info("Host Name :"+hostname+"\n Port Num :"+port+"\n Request Type :"+requesttype+"\n");
	        
	        HttpParams params = new SyncBasicHttpParams();
	        params
	            .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
	            .setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 10000)
	            .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
	            .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
	            .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
	            .setParameter(CoreProtocolPNames.USER_AGENT, "HttpComponents/1.1");
	        
	        /*IOReactorConfig ioconfig = new IOReactorConfig();
	        ioconfig.setConnectTimeout(10000);
	        ioconfig.setSoTimeout(5000);
	        ioconfig.setIoThreadCount(2);
	        ioconfig.setTcpNoDelay(true);*/
	        final ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(1,params);

	        HttpProcessor httpproc = new ImmutableHttpProcessor(new HttpRequestInterceptor[] {
	                new RequestContent(),
	                new RequestTargetHost(),
	                new RequestConnControl(),
	                new RequestUserAgent(),
	                new RequestExpectContinue()});
	        
	        // We are going to use this object to synchronize between the 
	        // I/O event and main threads
	        CountDownLatch requestCount = new CountDownLatch(1);
	        
	        BufferingHttpClientHandler handler = new BufferingHttpClientHandler(
	                httpproc,
	                new MyHttpRequestExecutionHandler(requestCount,requesttype,this),
	                new DefaultConnectionReuseStrategy(),
	                params);

	        handler.setEventListener(new EventLogger());
	        
	        final IOEventDispatch ioEventDispatch = new DefaultClientIOEventDispatch(handler, params);
	        
	        Thread t = new Thread(new Runnable() {
	         
	            @Override
	            public void run() {
	                try {
	                    ioReactor.execute(ioEventDispatch);
	                } catch (InterruptedIOException ex) {
	                    System.err.println("Interrupted");
	                } catch (IOException e) {
	                    System.err.println("I/O error: " + e.getMessage());
	                }
	                log.debug("Shutdown");
	            }
	            
	        });
	        t.start();

	        SessionRequest reqs = ioReactor.connect(
	                new InetSocketAddress(hostname, port), 
	                null, 
	                new HttpHost(hostname),
	                new MySessionRequestCallback(requestCount));
	       
	        // Block until all connections signal
	        // completion of the request execution
	        requestCount.await();
	        
	        log.debug("Shutting down I/O reactor");
	        
	        ioReactor.shutdown();
	        
	        log.debug("Shutting down I/O reactor completed");
	       
	    }
	    
	    static class MyHttpRequestExecutionHandler implements HttpRequestExecutionHandler {

	        private final static String REQUEST_SENT       = "request-sent";
	        private final static String RESPONSE_RECEIVED  = "response-received";
	        String requesttype;
	        FrontierClient fclient;
	        
	        private final CountDownLatch requestCount;
	        
	        public MyHttpRequestExecutionHandler(final CountDownLatch requestCount,String reqtype,FrontierClient fc) {
	            super();
	            this.requestCount = requestCount;
	            requesttype = reqtype;
	            fclient = fc;
	       }
	        
	        @Override
	        public void initalizeContext(final HttpContext context, final Object attachment) {
	            HttpHost targetHost = (HttpHost) attachment;
	            context.setAttribute(ExecutionContext.HTTP_TARGET_HOST, targetHost);
	        }
	        
	        @Override
	        public void finalizeContext(final HttpContext context) {
	            Object flag = context.getAttribute(RESPONSE_RECEIVED);
	            if (flag == null) {
	                // Signal completion of the request execution
	                requestCount.countDown();
	                //System.out.println("flag is null");
	            }
	        }

	        @Override
	        public HttpRequest submitRequest(final HttpContext context) {
	            //log.info("logfile creation is done");
	            HttpHost targetHost = (HttpHost) context.getAttribute(
	                    ExecutionContext.HTTP_TARGET_HOST);
	            Object flag = context.getAttribute(REQUEST_SENT);
	            if (flag == null) {
	                // Stick some object into the context
	                context.setAttribute(REQUEST_SENT, Boolean.TRUE);

	                log.debug("--------------");
	                log.debug("Sending request to " + targetHost);
	                log.debug("--------------");
	                
	               // return new BasicHttpRequest("GET", "/index.html");
	                
	                BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("POST", "/index.html");
//	                request.g
	            request.setHeader("reqtype",requesttype);
	            
	            log.debug("request type set in header is:"+requesttype);
	            
	            log.debug("creation  of request is over");
	            return request;
	            } 
	            else {
	                // No new request to submit
	            	log.debug("no new request to send");
	                return null;
	                
	            }
	        }
	        
	        @Override
	        public void handleResponse(final HttpResponse response, final HttpContext context) throws IOException {
	        	StatusLine st;
	            if(requesttype.equals("heartbeat")) {
	            	
	                    st = response.getStatusLine();
	                    //System.out.println("protocal version : "+st.getProtocolVersion()+"\n Response Phase: "+st.getReasonPhrase()+"\n Status code : "+st.getStatusCode());
	                    fclient.setresponsedetails(st.getReasonPhrase(), ""+st.getProtocolVersion(), st.getStatusCode());
	            }
	            else if(requesttype.equals("shutdown")){
	                	st = response.getStatusLine();
	                    //System.out.println("protocal version : "+st.getProtocolVersion()+"\n Response Phase: "+st.getReasonPhrase()+"\n Status code : "+st.getStatusCode());
	                    fclient.setresponsedetails(st.getReasonPhrase(), ""+st.getProtocolVersion(), st.getStatusCode());
	            }
	            
	            context.setAttribute(RESPONSE_RECEIVED, Boolean.TRUE);
	            
	            // Signal completion of the request execution
	            requestCount.countDown();
	        }
	        
	    }
	    
	    static class MySessionRequestCallback implements SessionRequestCallback {

	        private final CountDownLatch requestCount;        
	        
	        public MySessionRequestCallback(final CountDownLatch requestCount) {
	            super();
	            this.requestCount = requestCount;
	        }
	        
	        @Override
	        public void cancelled(final SessionRequest request) {
	        	log.debug("Connect request cancelled: " + request.getRemoteAddress());
	            this.requestCount.countDown();
	        }

	        @Override
	        public void completed(final SessionRequest request) {
	        	log.debug("Connect request completed: " + request.getRemoteAddress());
	        }

	        @Override
	        public void failed(final SessionRequest request) {
	        	log.debug("Connect request failed: " + request.getRemoteAddress());
	            this.requestCount.countDown();
	        }

	        @Override
	        public void timeout(final SessionRequest request) {
	        	log.debug("Connect request timed out: " + request.getRemoteAddress());
	            this.requestCount.countDown();
	        }
	        
	    }
	    
	    static class EventLogger implements EventListener {

	        @Override
	        public void connectionOpen(final NHttpConnection conn) {
	        	log.debug("Connection open: " + conn);
	            
	        }

	        @Override
	        public void connectionTimeout(final NHttpConnection conn) {
	        	log.debug("Connection timed out: " + conn);
	        }

	        @Override
	        public void connectionClosed(final NHttpConnection conn) {
	        	log.debug("Connection closed: " + conn);
	        }

	        @Override
	        public void fatalIOException(final IOException ex, final NHttpConnection conn) {
	            log.error("I/O error: ",ex);
	        }

	        @Override
	        public void fatalProtocolException(final HttpException ex, final NHttpConnection conn) {
	        	log.error("HTTP error: ", ex);
	        }
	        
	    }

}
